uint32_t HeapProfiler::CopyObjectMem2Buf(CVector<char *> &headerVec, CUnorderedMap<char *, uint32_t> &memBufMap, uint32_t objNum)
{
    auto currMemBuf = chunk_.NewArray<char>(PER_GROUP_MEM_SIZE);
    memBufMap.emplace(currMemBuf, 0);
    uint32_t lastOneNum = objNum % HEAD_NUM_PER_GROUP;
    auto currSize = 0;
    uint32_t totalSize = 0;
    uint32_t curOffset = objNum * sizeof(AddrTableItem);
    for (uint32_t i = 0; i < headerVec.size(); ++i) {
        auto headers = reinterpret_cast<AddrTableItem *>(headerVec[i]);
        auto max = headerVec.size();
        if (i == headerVec.size() - 1 && lastOneNum != 0) {
            max = lastOneNum;
        }
        for (uint32_t j = 0; j < max; ++j) {
            auto& item = headers[j];
            auto obj = reinterpret_cast<TaggedObject *>(item.addr);
            uint64_t objSize = obj->GetClass()->SizeFromJSHClass(obj);
            totalSize += objSize;
            if (currSize + objSize > PER_GROUP_MEM_SIZE) {
                memBufMap[currMemBuf] = currSize;
                auto bufSize = objSize > PER_GROUP_MEM_SIZE? objSize : PER_GROUP_MEM_SIZE;
                currSize = 0;
                currMemBuf = chunk_.NewArray<char>(bufSize);
                memBufMap.emplace(currMemBuf, 0);
            }
            item.objSize = objSize;
            item.offset = curOffset;
            auto ret = memcpy_s(currMemBuf + currSize, objSize, reinterpret_cast<void *>(item.addr), objSize);
            if (ret != 0) {
                LOG_ECMA(ERROR) << "ark BinaryDump CopyObjectMem memcpy_s failed";
                return totalSize;
            }
            curOffset += objSize;
            currSize += objSize;
        }
    }
    return totalSize;
}

template<typename Callback>
uint32_t HeapProfiler::GenObjTable(Callback IterateObjectsFunc, EntryIdMap &idMap, CVector<char *> &headerVec,
                                   CQueue<CVector<TaggedObject *>> &needStrObjQue)
{
    headerVec.push_back(chunk_.NewArray<char>(PER_GROUP_MEM_SIZE));
    uint32_t index = 0;
    uint32_t objNum = 0;
    auto table = reinterpret_cast<AddrTableItem *>(&headerVec[0]);
    CVector<TaggedObject *> *strIdObjVec = new CVector<TaggedObject *>();
    auto handleObj = [&index, &table, &idMap, &strIdObjVec, &objNum, &headerVec, &needStrObjQue, this](TaggedObject *obj) {
        JSTaggedValue value(obj);
        auto taggedType = value.GetRawData();
        auto [exist, id] = idMap.FindId(taggedType);
        if (!exist) {
            idMap.InsertId(taggedType, id);
        }
        table[index].addr = reinterpret_cast<uint64_t>(obj);
        table[index].id = id;
        if (value.IsOnlyJSObject() || value.IsJSFunction()) {
            strIdObjVec->push_back(obj);
        }
        index++;
        if (index == HEAD_NUM_PER_GROUP) {
            headerVec.push_back(chunk_.NewArray<char>(PER_GROUP_MEM_SIZE));
            objNum += HEAD_NUM_PER_GROUP;
            index = 0;
            table = reinterpret_cast<AddrTableItem *>(&headerVec[headerVec.size() - 1]);
        }
        if (strIdObjVec->size() > 50) { // 50: num is 50 need to generate str
            this->MoveNeedStrObj2Que(strIdObjVec, needStrObjQue);
            strIdObjVec = new CVector<TaggedObject *>();
        }
        // objNum += index;
    };
    IterateObjectsFunc(handleObj, false);
    objNum += index;
    if (strIdObjVec->size() > 0) {
        MoveNeedStrObj2Que(strIdObjVec, needStrObjQue);
    } else {
        delete strIdObjVec;
    }
    if (index == 0) {
        chunk_.Delete<char>(headerVec[headerVec.size() - 1]);
        headerVec.pop_back();
    }
    return objNum;
}

uint32_t HeapProfiler::WriteToBinFile(Stream *stream, CVector<char *> &headerVec, CUnorderedMap<char *, uint32_t> &memBufMap,
                                      std::atomic<uint32_t> &fileOffset, CVector<uint32_t> &headVec)
{
    uint32_t headSize = headVec.size() * sizeof(uint32_t);
    uint32_t totalSize = headVec[0] * sizeof(AddrTableItem) + headVec[2] + headSize;
    uint32_t lastOneNum = headVec[0] % HEAD_NUM_PER_GROUP;
    LockHolder lock(fileMtx_); // lock file fd, then update fileOffset
    uint32_t currOffset = fileOffset.fetch_add(totalSize);
    stream->WriteBinBlock(reinterpret_cast<char *>(headVec.data()), headSize);
    for (uint32_t i = 0; i < headerVec.size(); ++i) {
        auto headers = headerVec[i];
        auto max = headerVec.size();
        if (i == headerVec.size() - 1 && lastOneNum != 0) {
            max = lastOneNum;
        }
        stream->WriteBinBlock(headers, max);
        chunk_.Delete<char>(headers);
    }
    for (auto memItem : memBufMap) {
        stream->WriteBinBlock(memItem.first, memItem.second);
        chunk_.Delete<char>(memItem.first);
    }
    return currOffset;
}

void HeapProfiler::MoveNeedStrObj2Que(CVector<TaggedObject *> *strIdObjVec, CQueue<CVector<TaggedObject *>> &needStrObjQue)
{
    LockHolder lock(queMtx_);
    needStrObjQue.push(*strIdObjVec);
}

bool HeapProfiler::MoveNeedStrObj2Vec(CVector<TaggedObject *> **strIdObjVec, CQueue<CVector<TaggedObject *>> &needStrObjQue)
{
    LockHolder lock(queMtx_);
    if (needStrObjQue.empty()) {
        return false;
    }
    if (!needStrObjQue.empty()) {
        *strIdObjVec = &needStrObjQue.front();
        needStrObjQue.pop();
    }
    return true;
}

template<typename Callback>
bool HeapProfiler::DumpRawHeap(Callback IterateObjectsFunc, EntryIdMap &idMap, Stream *stream, std::atomic<uint8_t> &finFlag,
              std::atomic<uint32_t> &fileOffset, CQueue<CVector<TaggedObject *>> &needStrObjQue, uint32_t &currOffset)
{
    CVector<char *> headerVec;
    uint32_t objNum = GenObjTable(IterateObjectsFunc, idMap, headerVec, needStrObjQue);
    CUnorderedMap<char *, uint32_t> memBufMap;
    uint32_t heapSize = CopyObjectMem2Buf(headerVec, memBufMap, objNum);
    CVector<uint32_t> headVec{objNum, heapSize};
    currOffset = WriteToBinFile(stream, headerVec, memBufMap, fileOffset, headVec);
    finFlag.fetch_add(1);
    return true;
}

bool HeapProfiler::BinaryDump(Stream *stream, [[maybe_unused]] const DumpSnapShotOption &dumpOption)
{
    CQueue<CVector<TaggedObject *>> needStrObjQue;
    std::atomic<uint8_t> finFlag(0);
    std::atomic<uint32_t> fileOffset(0);
    uint32_t tdNum = 2;
    CVector<uint32_t> secOffVec(tdNum);
    auto heapThread = [this, &stream, &finFlag, &fileOffset, &needStrObjQue, &secOffVec]() {
        auto cb = [this](void (*func)(TaggedObject *)) { vm_->GetHeap()->IterateOverObjects(func); };
        DumpRawHeap(cb, entryIdMap_, stream, finFlag, fileOffset, needStrObjQue, secOffVec[0]);
    };
    auto shareThread = [this, &stream, &finFlag, &fileOffset, &needStrObjQue, &secOffVec]() {
        auto cb = [this](void (*func)(TaggedObject *)) { vm_->GetHeap()->IterateOverLocalToShareObjects(func); };
        DumpRawHeap(cb, entryIdMap_, stream, finFlag, fileOffset, needStrObjQue, secOffVec[1]);
    };
    uint32_t currOffset = 0;
    std::thread localTd(heapThread);
    localTd.join();
    std::thread shareTd(shareThread);
    localTd.join();
    CVector<TaggedObject *> *strIdObjVec = nullptr;
    CUnorderedMap<uint64_t, CVector<TaggedObject *>> strIdMapObjVec;
    DumpSnapShotOption op;
    auto *snapshot = GetChunk()->New<HeapSnapshot>(vm_, GetEcmaStringTable(), op, false, entryIdMap_, GetChunk());
    while (true) {
        bool haveData = MoveNeedStrObj2Vec(&strIdObjVec, needStrObjQue);
        if (!haveData && finFlag == tdNum) { // 2 : all thread is finished, and que no data, dump finished
            haveData = MoveNeedStrObj2Vec(&strIdObjVec, needStrObjQue);
            if (!haveData) {
                break;
            }
        }
        if (!haveData || strIdObjVec == nullptr) {
            std::this_thread::sleep_for(std::chrono::milliseconds(10));
            continue;
        }
        for (auto obj : *strIdObjVec) {
            auto stringId = snapshot->GenerateStringId(obj);
            if (strIdMapObjVec.find(stringId) == strIdMapObjVec.end()) {
                strIdMapObjVec.emplace(stringId, CVector<TaggedObject *>());
            }
            strIdMapObjVec[stringId].push_back(obj);
        }
        delete strIdObjVec;
    }
    HeapSnapshotJSONSerializer::DumpStringTable(snapshot, stream);
    GetChunk()->Delete(snapshot);
}




#define PER_GROUP_MEM_SIZE (256 * 1024 * 1024) // 256MB
#define HEAD_NUM_PER_GROUP (PER_GROUP_MEM_SIZE / sizeof(AddrTableItem)) // 8388608 num in one group
    uint32_t WriteToBinFile(Stream *stream, CVector<char *> &headerVec, CUnorderedMap<char *, uint32_t> &memBufMap,
                            std::atomic<uint32_t> &fileOffset, CVector<uint32_t> &headVec);
    void MoveNeedStrObj2Que(CVector<TaggedObject *> *strIdObjVec, CQueue<CVector<TaggedObject *>> &needStrObjQue);
    bool MoveNeedStrObj2Vec(CVector<TaggedObject *> **strIdObjVec, CQueue<CVector<TaggedObject *>> &needStrObjQue);
    uint32_t CopyObjectMem2Buf(CVector<char *> &headerVec, CUnorderedMap<char *, uint32_t> &memBufMap, uint32_t objNum);
    template<typename Callback>
    uint32_t GenObjTable(Callback &IterateObjectsFunc, EntryIdMap &idMap, CVector<char *> &headerVec,
                                   CQueue<CVector<TaggedObject *>> &needStrObjQue);
    template<typename Callback>
    bool DumpRawHeap(Callback &IterateObjectsFunc, EntryIdMap &idMap, Stream *stream, std::atomic<uint8_t> &finFlag,
                std::atomic<uint32_t> &fileOffset, CQueue<CVector<TaggedObject *>> &needStrObjQue, uint32_t &currOffset);